如何用编程得出泰坦尼克号生还者的年龄段?
【CSDN编者按】大家熟知的电影《泰坦尼克号》,是一部经典的奥斯卡电影,也是一部以真实故事改编而拍的电影。
真实故事中,1912年4月14日,这艘当时世界上体积最庞大、内部设施最豪华的客运轮船泰坦尼克号,与一座冰山相撞,2224名船员及乘客中,逾1500人丧生,其中仅333具罹难者遗体被寻回。
时隔一个世纪之久,如果用编程的角度,来审视这场灾难,会有什么发现呢?今天的文章,正是用编程来研究泰坦尼克号的生还者情况。
作者 | 光城
责编 | 胡巍巍
数据预处理
▌数据初识
这里主要是对数据进行介绍,下载的数据集分为train与test以及gender_submission,分别是训练集,测试集以及生成提交文件的参考文件。
train与test各列分别为:
PassengerId 乘客ID
Pclass 客舱等级(1/2/3等舱位)
Name 乘客姓名
Sex 性别
Age 年龄
SibSp 兄弟姐妹数或配偶数
Parch 父母数或子女数
Ticket 船票编号
Fare 船票价格
Cabin 客舱号
Embarked 登船港口
▌导包
import seaborn as sns
from matplotlib import pyplot as plt
import pandas as pd
%matplotlib inline
import warnings
from sklearn.model_selection import train_test_split
warnings.filterwarnings('ignore')
▌数据导入
这里将训练集与测试集导入,并通过concat将数据整合一块,做个缺失值分析处理。
tannike_train=pd.read_csv('./train.csv')
tannike_test=pd.read_csv('./test.csv')
join_data=pd.concat([tannike_train,tannike_test],ignore_index=True)
▌数据预处理及特征工程
统计缺失值的代码如下:
join_data.isna().sum()
结果:
Age 263
Cabin 1014
Embarked 2
Fare 1
Name 0
Parch 0
PassengerId 0
Pclass 0
Sex 0
SibSp 0
Survived 418
Ticket 0
dtype: int64
从这个结果中我们发现:
Age与Cabin以缺失值较多,而Fare与Embarked缺失值较少,可以考虑用中位数,均值或者众数等方式解决。
▌缺失值处理
fare_mean = tannike_train['Fare'].mean()
tannike_test.loc[pd.isnull(tannike_test.Fare),'Fare'] = fare_mean
embarked_mode = tannike_train['Embarked'].mode()
tannike_train.loc[pd.isna(tannike_train.Embarked),['Embarked']] = embarked_mode[0]
tannike_train.loc[pd.isna(tannike_train.Age),['Age']] = tannike_train['Age'].mean()
tannike_test.loc[pd.isna(tannike_test.Age),['Age']] = tannike_test['Age'].mean()
查看缺失值,以Embarked为例:
tannike_train.Embarked.isna().sum()
输出为0,则表示缺失值被处理完毕!
▌数据划分
这里直接划分成训练集与测试集的原因是对比与label数据的误差度。
label = tannike_train['Survived']
tannike_train.drop('Survived',axis=1,inplace=True)
X_train,X_test,Y_train,Y_test = train_test_split(tannike_train,label,test_size = 0.3,random_state = 1)
X_train['Survived'] = Y_train
X_test['Survived'] = Y_test
切分数据为训练集,测试集!
▌性别与生存
我们先来看一下性别与生存关系:
sex_Sur = pd.crosstab(tannike_train.Sex,tannike_train.Survived)
sex_Sur.rename(columns={0.0:'dead',1.0:'survived'},inplace=True)
sex_Sur
输出:
Survived dead survived
Sex
female 81 233
male 468 109
数据可视化:
# stacked属性设置多个柱状图是否叠加
sex_Sur.plot.bar(stacked=True,color=['#f441f1','#b6f442'])
plt.xticks(rotation=0,size='x-large')
plt.xlabel('')
条状图转为饼图:
sex_Sur.plot.pie(subplots=True,figsize=(10,5),colors=('#b6f442','#41d0f4'))
综合上述两图,我们发现女性的存活率要比男性高!Age可以作为一个重要特征!
特征工程处理:将数据转为0,1数据。
tannike_train['Sex'] = tannike_train['Sex'].apply(lambda x: 1 if x == 'male' else 0)
tannike_test['Sex'] = tannike_test['Sex'].apply(lambda x: 1 if x == 'male' else 0)
# one-hot编码
tannike_train = pd.get_dummies(data= tannike_train,columns=['Sex'])
tannike_test = pd.get_dummies(data= tannike_test,columns=['Sex'])
▌年龄与存活
研究年龄与存活关联:
survived_age=tannike_train[tannike_train.Survived==1]['Age']
dead_age=tannike_train[tannike_train.Survived==0]['Age']
age_frame=pd.concat([survived_age,dead_age],axis=1)
age_frame.columns=['Survived','Dead']
age_frame.head()
输出数据(前5行数据):
Survived Dead
0 NaN 22.0
1 38.0 NaN
2 26.0 NaN
3 35.0 NaN
4 NaN 35.0
可视化:
# 为避免颜色覆盖,使用alpha通道属性
age_frame.plot(kind='hist',bins=30,alpha=0.3,figsize=(10,6))
上图发现:
年龄低于5岁的,存活率相对较高!而在75岁以后存活率也高,在中间的15到35之间存活率相对较高,其他的就比较低了,我们可以在做特征工程时候,对其做年龄划分处理,比如:低中高等。
特征工程处理:
tannike_train['Small_Age'] = np.int32(tannike_train['Age'] <= 5)
tannike_train['Old_Age'] = np.int32(tannike_train['Age'] >= 65)
tannike_train['Middle_Age'] = np.int32((tannike_train['Age'] >= 15) & (tannike_train['Age'] <= 25))
tannike_test['Small_Age'] = np.int32(tannike_test['Age'] <= 5)
tannike_test['Old_Age'] = np.int32(tannike_test['Age'] >= 65)
tannike_test['Middle_Age'] = np.int32((tannike_test['Age'] >= 15) & (tannike_test['Age'] <= 25))
▌名字与存活
首先来统计一下各个名字开头的数量,比如Ms开头的数量:
# 比如名字为Attalah, Miss. Malake,我们提取出目的是Miss,运用两次lambda
X_train['Name_Title'] = X_train['Name'].apply(lambda x: x.split(',')[1]).apply(lambda x: x.split()[0])
X_test['Name_Title'] = X_test['Name'].apply(lambda x: x.split(',')[1]).apply(lambda x: x.split()[0])
X_train.groupby('Name_Title')['Survived'].count()
输出:
Name_Title
Capt. 1
Col. 2
Don. 1
Dr. 4
Lady. 1
Major. 1
Master. 27
Miss. 126
Mlle. 1
Mme. 1
Mr. 365
Mrs. 87
Rev. 5
the 1
Name: Survived, dtype: int64
交叉数据:训练与测试集可视化对比差异:
# 条形图上的误差棒则表示各类的数值相对于条形图所显示的值的误差
# seaborn的barplot()利用矩阵条的高度反映数值变量的集中趋势,以及使用errorbar功能(差棒图)来估计变量之间的差值统计
fig, (axis1,axis2) = plt.subplots(1,2,figsize=(15,5))
sns.barplot('Name_Title', 'Survived', data=X_train.sort_values('Name_Title'), ax=axis1)
sns.barplot('Name_Title', 'Survived', data=X_test.sort_values('Name_Title'), ax=axis2)
下面这个图显示的数据变量的集中趋势!
对比这个数据,得出如下结论:对于不同的名字开头,他的生存率不同,这里根据存活率平均程度高低依次下分。
特征工程处理:通过定义一个函数来对名字的存活性高低,进行详细排序:
def Name_Title_Code(x):
if x == 'Mr.':
return 1
if (x=='Ms.') or (x=='Lady.') or (x == 'Mlle.') or (x =='Mme.') or (x == 'the.') or (x =='Sir.') or (x=='Major'):
return 2
if (x == 'Mrs.'):
return 3
if x == 'Miss.':
return 4
if x == 'Master.':
return 5
if x == 'Dr.':
return 6
return 7
对真正的训练集与测试集进行one-hot编码:
tannike_train['Name_Title'] = tannike_train['Name'].apply(lambda x: x.split(',')[1]).apply(lambda x: x.split()[0])
tannike_test['Name_Title'] = tannike_test['Name'].apply(lambda x: x.split(',')[1]).apply(lambda x: x.split()[0])
tannike_train['Name_Title'] = tannike_train['Name_Title'].apply(Name_Title_Code)
tannike_test['Name_Title'] = tannike_test['Name_Title'].apply(Name_Title_Code)
tannike_train = pd.get_dummies(columns = ['Name_Title'], data = tannike_train)
tannike_test = pd.get_dummies(columns = ['Name_Title'], data = tannike_test)
▌船票编号与存活
获取船票第一个字母:
def Ticket_First_Let(x):
return x[0]
交叉数据集:训练与测试集处理:
X_train['Ticket_First_Letter'] = X_train['Ticket'].apply(Ticket_First_Let)
X_test['Ticket_First_Letter'] = X_test['Ticket'].apply(Ticket_First_Let)
可视化船票编号与存活关系:
fig, (axis1,axis2) = plt.subplots(1,2,figsize=(15,5))
sns.barplot('Ticket_First_Letter', 'Survived', data=X_train.sort_values('Ticket_First_Letter'), ax=axis1)
sns.barplot('Ticket_First_Letter', 'Survived', data=X_test.sort_values('Ticket_First_Letter'), ax=axis2)
同理,我们发现票的前面数字的编号对生存也是有影响!这里对不同的票编号封装成函数,进行处理!
def Ticket_First_Letter_Code(x):
if (x == 'F'):
return 1
if x == '1' or x=='P' or x=='9':
return 2
if x == '2':
return 3
if x == 'C':
return 4
if x == 'S':
return 5
if x == '6':
return 6
if x == '7':
return 7
if x == 'A':
return 8
if x == 'W':
return 9
return 10
真实训练集与测试集处理:
one-hot编码处理:
tannike_train['Ticket_First_Letter'] = tannike_train['Ticket'].apply(Ticket_First_Let)
tannike_test['Ticket_First_Letter'] = tannike_test['Ticket'].apply(Ticket_First_Let)
tannike_train['Ticket_First_Letter'] = tannike_train['Ticket_First_Letter'].apply(Ticket_First_Letter_Code)
tannike_test['Ticket_First_Letter'] = tannike_test['Ticket_First_Letter'].apply(Ticket_First_Letter_Code)
tannike_train = pd.get_dummies(columns = ['Ticket_First_Letter'], data = tannike_train)
tannike_test = pd.get_dummies(columns = ['Ticket_First_Letter'], data = tannike_test)
▌客舱号与生存
Cabin表示客舱号。首先我们来对数据缺失值,进行填充。将缺失值设为NA。
X_train['Cabin'] = X_train['Cabin'].fillna('NA')
X_test['Cabin'] = X_test['Cabin'].fillna('NA')
其次定义一个方法:用于获取客舱号的第一个字母或数字。
def Cabin_First_Letter(x):
if x == 'NA':
return 'NA'
return x[0]
将交叉验证集数据进行上述方法操作。
sns.barplot('Cabin_First_Letter', 'Survived', data=X_train.sort_values('Cabin_First_Letter'), ax=axis1)
X_train['Cabin_First_Letter'] = X_train['Cabin'].apply(Cabin_First_Letter)
X_test['Cabin_First_Letter'] = X_test['Cabin'].apply(Cabin_First_Letter)
统计客舱号中第一个字母的出现次数:
X_train.groupby('Cabin_First_Letter')['Survived'].count()
结果:
Cabin_First_Letter
A 12
B 28
C 41
D 21
E 22
F 8
G 3
NA 488
Name: Survived, dtype: int64
接着,数据处理完了,我们是时候可视化数据,并确定该特征值与生存的关系!
fig, (axis1,axis2) = plt.subplots(1,2,figsize=(15,5))
sns.barplot('Cabin_First_Letter', 'Survived', data=X_train.sort_values('Cabin_First_Letter'), ax=axis1)
sns.barplot('Cabin_First_Letter', 'Survived', data=X_test.sort_values('Cabin_First_Letter'), ax=axis2)
这里采用Seaborn的barplot绘制柱状图,图中的竖线代表误差棒!图显示的是每一个客舱号第一位数字或字母出现的集中分布趋势!
针对上述这个图,我们得出如下结论:
存活率排序:E>D>>B>C>A>.....
然后定义一个方法,进行封装,依次返回数值!
def Cabin_First_Letter_Code(x):
if x == 'E':
return 1
if x == 'D':
return 2
if x == 'B':
return 3
if x == 'C':
return 4
if x == 'A':
return 5
return 6
然后根据上述的交叉验证数据集的结果,应用到我们的测试集与训练集上。
tannike_train['Cabin'] = tannike_train['Cabin'].fillna('NA')
tannike_test['Cabin'] = tannike_test['Cabin'].fillna('NA')
tannike_train['Cabin_First_Letter'] = tannike_train['Cabin'].apply(Cabin_First_Letter)
tannike_test['Cabin_First_Letter'] = tannike_test['Cabin'].apply(Cabin_First_Letter)
由于当前研究的这个数据为等级资料数据,我们这里用one-hot编码可有效的处理问题:
tannike_train['Cabin_First_Letter'] = tannike_train['Cabin_First_Letter'].apply(Cabin_First_Letter_Code)
tannike_test['Cabin_First_Letter'] = tannike_test['Cabin_First_Letter'].apply(Cabin_First_Letter_Code)
tannike_train = pd.get_dummies(columns = ['Cabin_First_Letter'], data = tannike_train)
tannike_test = pd.get_dummies(columns = ['Cabin_First_Letter'], data = tannike_test)
▌登陆港口与生存
由于登录港口数据已经在前一节的缺失值处理掉了,这里便可以直接对交叉验证集数据可视化:
fig, (axis1,axis2) = plt.subplots(1,2,figsize=(15,5))
sns.barplot('Embarked', 'Survived', data=X_train.sort_values('Embarked'), ax=axis1)
sns.barplot('Embarked', 'Survived', data=X_test.sort_values('Embarked'), ax=axis2)
然后我们对其进行生存排序:C>Q>S,由于只有三类,直接对训练集与测试集进行one-hot编码即可!
tannike_train = pd.get_dummies(tannike_train,columns = ['Embarked'])
tannike_test = pd.get_dummies(tannike_test,columns = ['Embarked'])
Pclass客舱等级(1/2/3等舱位)同Embarked操作,这个就不阐述了!
▌兄弟姐妹与生存
SibSp:兄弟姐妹数或配偶数,Parch:父母数或子女数,将其与Parch 合并处理!
X_train['Fam_Size'] = X_train['SibSp'] + X_train['Parch']
X_test['Fam_Size'] = X_test['SibSp'] + X_test['Parch']
可视化:
fig, (axis1,axis2) = plt.subplots(1,2,figsize=(15,5))
sns.barplot('Fam_Size', 'Survived', data=X_train.sort_values('Parch'), ax=axis1)
sns.barplot('Fam_Size', 'Survived', data=X_test.sort_values('Parch'), ax=axis2)
特征处理:分为三类:Solo,Nuclear,Big,并对训练集与测试集进行one-hot编码!
import numpy as np
def Family_feature(train, test):
for i in [train, test]:
i['Fam_Size'] = np.where((i['SibSp']+i['Parch']) == 0 , 'Solo',
np.where((i['SibSp']+i['Parch']) <= 3,'Nuclear', 'Big'))
del i['SibSp']
del i['Parch']
return train, test
tannike_train, tannike_test = Family_feature(tannike_train, tannike_test)
tannike_train = pd.get_dummies(tannike_train,columns = ['Fam_Size'])
tannike_test = pd.get_dummies(tannike_test,columns = ['Fam_Size'])
Fare同理操作!
特征选择
这里以全集特征训练!删除某一特征训练结果测试。
X_train_ = tannike_train.loc[X_train.index]
X_test_ = tannike_train.loc[X_test.index]
Y_train_ = label.loc[X_train.index]
Y_test_ = label.loc[X_test.index]
X_test_.head(3)
X_test_ = X_test_[X_train_.columns]
X_test_.head(3)
tannike_test = tannike_test[tannike_train.columns]
tannike_test.head()
模型选择
▌xgboost
这里选用xgboost进行测试,下面是选择的最优情况的代码,线下测试:0.8097014925373134,线上测试0.80621!
import xgboost as xgb
from sklearn.preprocessing import LabelEncoder
gbm = xgb.XGBClassifier(max_depth=3, n_estimators=300, learning_rate=0.06,scoring='r3', cv=10, verbose=1, n_jobs=4,)
gbm.fit(X_train_, Y_train_)
gbm.score(X_test_,Y_test_)
▌随机森林
线下测试:0.8022388059701493,线上测试0.80621!
rf_ = RandomForestClassifier(random_state = 10, warm_start = True,
n_estimators = 900,
max_depth = 6,
max_features = 'sqrt',min_samples_leaf=1)
rf_.fit(X_train_, Y_train_)
rf_.score(X_test_,Y_test_)
结果上传
首先通过上述建立的模型进行预测,并输出到上传文件中,然后再上传 !
s = pd.read_csv('./gender_submission.csv')
predictions = p.predict(tannike_test) # p为模型分类器
submission = pd.DataFrame({"PassengerId": s.PassengerId, "Survived": predictions.astype(np.int32)})
submission.to_csv("submission-pipe.csv", index=False)
总结
泰坦尼克号数据大多属于等级资料,需要用one-hot编码;
特征选择,加权的重要性;
交叉验证集构建;
多模型选择与模型融合;
当前这个模型单一,特征提取没有进行逐一删选,后续。
参考:
https://www.kesci.com/home/project/5af18c294b7639369e6c289c
https://blog.csdn.net/wydyttxs/article/details/76695205
作者简介:光城,研一,个人公众号:guangcity,博客:http://light-city.me/, 个人研究方向:知识图谱,正致力于将机器学习运用到KG当中。
个人公众号简介:旨在推送一些个人学习python的一些历程,包括python爬虫,机器学习,LeetCode(每周更新2篇+)等。
微信改版了,
想快速看到CSDN的热乎文章,
赶快把CSDN公众号设为星标吧,
打开公众号,点击“设为星标”就可以啦!
“征稿啦”
CSDN 公众号秉持着「与千万技术人共成长」理念,不仅以「极客头条」、「畅言」栏目在第一时间以技术人的独特视角描述技术人关心的行业焦点事件,更有「技术头条」专栏,深度解读行业内的热门技术与场景应用,让所有的开发者紧跟技术潮流,保持警醒的技术嗅觉,对行业趋势、技术有更为全面的认知。
如果你有优质的文章,或是行业热点事件、技术趋势的真知灼见,或是深度的应用实践、场景方案等的新见解,欢迎联系 CSDN 投稿,联系方式:微信(guorui_1118,请备注投稿+姓名+公司职位),邮箱(guorui@csdn.net)。
推荐阅读: